from sqlalchemy.sql import and_, or_, null
from sqlalchemy.orm import object_mapper, ColumnProperty
from sqlalchemy.orm.collections import MappedCollection

from brainfreeze import OneToOneMapperProperty

__all__ = ['Base', 'effective_between', 'KeymasterCollection']

class Base(object):
    """Convenient base class for SQLAlchemy mapped clases."""
    def __init__(self, **kwargs):
        """Populate instance from keyword arguments"""
        for key, value in kwargs.iteritems():
            setattr(self, key, value)
        return super(Base, self).__init__()

    def __repr__(self):
        """Print representations of properties mapped to this class."""
        class_ = self.__class__
        properties = [
            p for p in object_mapper(self).iterate_properties 
            if isinstance(p, ColumnProperty) or isinstance(p, OneToOneMapperProperty)
        ]
        return "%s(%s)" % (
            class_.__name__,
            ", ".join(["%s=%r" % (p.key, getattr(self, p.key)) for p in properties])
        )
        
def effective_between(value, start_column, end_column):
    """Return a 'BETWEEN' predicate clause for 'effective' dates."""
    return and_(
        or_(start_column == null(), start_column <= value),
        or_(end_column == null(), end_column >= value)
    )

class KeymasterCollection(MappedCollection):
    """A dict-like SQLAlchemy Mapped Collection with some extra goodies
    
    KeymasterDict is a dict that populates missing values via a factory function:
        X = d['someval']   # will create an Object for that key if one is not already present.
    
    KeymasterDict also overrides __setitem__ to tell items their keys:
        d['someval'] = Object()   # will call setattr(newobj, keyattr, 'someval')

    """
    def __init__(self, factory, keyattr, *args, **kwargs):
        self.factory = factory
        self.keyattr = keyattr
        self.keyfunc = lambda obj: getattr(obj, keyattr)
        super(KeymasterCollection, self).__init__(self.keyfunc)

    def __missing__(self, key):
        """Create and return a missing object"""
        self[key] = val = self.factory(key)
        return val

    def __setitem__(self, key, value):
        """Add an object to the dict and let it know its key"""
        setattr(value, self.keyattr, key)
        super(KeymasterCollection, self).__setitem__(key, value)